#include <CL/cl.h>
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>

using namespace std;

#define TEST_DATA_LEN 10
#define NUM_MEM_OBJ 3

// create OpenCL context
cl_context create_context() {
	cl_int status = 0;
	cl_uint num_platform = 0;
	cl_platform_id platform = NULL;
	cl_context context = NULL;

	/*不同OpenCL组织里不同厂商的不同硬件都纷纷支持OpenCL标准，而每个支持者都会独自去实现OpenCl的具体实现
	*这里的每套实现都是一个platform，要在程序开始查询机器所有支持的platform，再根据情况选择一个合适的paltform
	*/
	// get OpenCL platform 获取平台数量
	status = clGetPlatformIDs(0, NULL, &num_platform);

	if (status != CL_SUCCESS || num_platform == 0) {
		cout << "Failed to call clGetPlatformIDs" << endl;
		return NULL;
	}

	if (num_platform > 0) {
		cl_platform_id *platforms =
			(cl_platform_id *)malloc(num_platform * sizeof(cl_platform_id));
		status = clGetPlatformIDs(num_platform, platforms, NULL);

		if (status != CL_SUCCESS)
			return NULL;

		for (int i = 0; i < num_platform; ++i) {
			static char tmp_buf[1024];
			status = clGetPlatformInfo(platforms[i], CL_PLATFORM_VENDOR,
					sizeof(tmp_buf), tmp_buf, NULL); /*获取各个平台的详细信息*/
			printf("%s\n", tmp_buf);
			platform = platforms[i];
			// if (platform)
			//	break;
		}

		free(platforms);
	}

	// 根据选定的platform创建对应的上下文
	cl_context_properties context_prop[] = {
		CL_CONTEXT_PLATFORM, (cl_context_properties)platform, 0};

	context = clCreateContextFromType(context_prop, CL_DEVICE_TYPE_GPU,
			NULL, NULL, &status);
	if (status != CL_SUCCESS) {
		printf("failed to create GPU context\n");
		context = clCreateContextFromType(context_prop, CL_DEVICE_TYPE_CPU,
				NULL, NULL, &status);

		if (status != CL_SUCCESS) {
			printf("Failed to create CPU context\n");
			return NULL;
		}
	}

	return context;
}

// create command queue  创建命令队列 
cl_command_queue create_cmd_queue(cl_context context,
		cl_device_id *out_device) {
	cl_int status;
	cl_device_id *p_devices;
	cl_command_queue cmd_queue = NULL;
	size_t param_size = -1;

	// 获取上下文中的device列表
	status = clGetContextInfo(context, CL_CONTEXT_DEVICES, 0, NULL,
			&param_size);
	if (status != CL_SUCCESS) {
		printf("Failed to run clGetContextInfo\n");
		return NULL;
	}

	if (param_size <= 0) {
		printf("Failed to get device buffer info\n");
		return NULL;
	}

	printf("parameter size: %lu\n", param_size);

	// allocate memory for device structure
	p_devices = (cl_device_id *)malloc(param_size / sizeof(cl_device_id));
	status = clGetContextInfo(context, CL_CONTEXT_DEVICES, param_size,
			p_devices, NULL);
	if (status != CL_SUCCESS) {
		printf("Failed to get devices buffer\n");
		free(p_devices);
		return NULL;
	}
	//为对应的p_devices创建命令队列，是像每个device发送指令的信使
	cmd_queue = clCreateCommandQueue(context, p_devices[0], 0, &status);
	if (cmd_queue == NULL) {
		printf("Failed to create command queue\n");
		return NULL;
	}

	*out_device = p_devices[0];
	free(p_devices);

	return cmd_queue;
}

//  创建opencl   program
cl_program create_program(cl_context context, cl_device_id device,
		const char *file_name) {
	cl_int status;
	cl_program program;
	char *program_string;

	size_t len;
	FILE *fp;

	fp = fopen(file_name, "rb");
	if (fp == NULL) {
		return NULL;
	}

	fseek(fp, 0, SEEK_END);
	len = ftell(fp);
	program_string = (char *)malloc((len + 1) * sizeof(char));
	rewind(fp);
	len = fread(program_string, 1, len, fp);//读取源码字符串到program_string中
	program_string[len] = '\0';
	fclose(fp);

	program =
		clCreateProgramWithSource(context, 1, (const char **)&program_string, NULL, NULL);//根据源码创建program
	if (program == NULL) {
		cout << "Failed to create program from cl source\n" << endl;
		return NULL;
	}

	status = clBuildProgram(program, 0, NULL, NULL, NULL, NULL);//编译program
	if (status != CL_SUCCESS) {
		char *log_buf;
		size_t log_size;

		cout << "failed to build kernel, dump build log" << endl;

		status = clGetProgramBuildInfo(program, device, CL_PROGRAM_BUILD_LOG, 0,
				NULL, &log_size);
		log_buf = (char *)malloc(log_size * sizeof(char));
		status = clGetProgramBuildInfo(program, device, CL_PROGRAM_BUILD_LOG,
				log_size, log_buf, NULL);
		printf("%s\n", log_buf);
		free(log_buf);
	}

	return program;
}

// 创建opencl  buffer对象，申请的对象为device  memory
int create_memobj(cl_context context, cl_mem mem_obj[NUM_MEM_OBJ], float *f_a,
		float *f_b) {
	//创建buffer对象大小为sizeof(float) * TEST_DATA_LEN，memory属性为只读，buffer数据与host memroy f_a数据同步
	mem_obj[0] = clCreateBuffer(context, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR,
			sizeof(float) * TEST_DATA_LEN, f_a, NULL); 

	//创建buffer对象大小为sizeof(float) * TEST_DATA_LEN，memory属性为只读，buffer数据与host memroy f_b数据同步
	mem_obj[1] = clCreateBuffer(context, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR,
			sizeof(float) * TEST_DATA_LEN, f_b, NULL);
	//创建buffer对象大小为sizeof(float) * TEST_DATA_LEN，memory属性为可读写
	mem_obj[2] = clCreateBuffer(context, CL_MEM_READ_WRITE,
			sizeof(float) * TEST_DATA_LEN, NULL, NULL);
	//检查buffer创建结果是否成功
	for(int i = 0; i < NUM_MEM_OBJ; i++) {
		if (mem_obj[i] == NULL) {
			printf("Failed to create memory objects\n");
			return -1;
		}
	}

	return 0;
}

// 释放opencl 的各种资源
void release_resource(cl_context context, cl_command_queue cmd_queue,
		cl_program program, cl_kernel kernel, cl_mem mem_obj[NUM_MEM_OBJ]) {
	for (int i = 0; i < NUM_MEM_OBJ; i++) {
		if (mem_obj[i] != NULL)
			clReleaseMemObject(mem_obj[i]);//释放buffer
	}
	if (cmd_queue != NULL)
		clReleaseCommandQueue(cmd_queue);//释放命令队列

	if (kernel != NULL)
		clReleaseKernel(kernel);//释放kernel

	if (program != NULL)
		clReleaseProgram(program);//释放program

	if (context != NULL)
		clReleaseContext(context);//释放上下文资源
}

// entry point
int main(int arg, const char *argv[]) {
	cl_context context = 0;
	cl_command_queue cmd_queue = 0;
	cl_program program = 0;
	cl_device_id device = 0;
	cl_kernel kernel = 0;
	cl_mem mem_obj[NUM_MEM_OBJ] = {0};
	cl_int status;

	cout << "OpenCL host program start" << endl;

	// 创建上下文
	context = create_context();
	if (context == NULL) {
		cout << "Failed to create OpenCL context" << endl;
		return 0;
	}

	// 创建命令队列
	cmd_queue = create_cmd_queue(context, &device);
	if (cmd_queue == NULL) {
		cout << "Failed to createCommand Queue\n" << endl;
		release_resource(context, cmd_queue, program, kernel, mem_obj);
		return 0;
	}

	// 使用源码创建program
	program = create_program(context, device, "add.cl");//add.cl中定义了opencl c语言源码
	if (program == NULL) {
		cout << "Failed to createProgram" << endl;
		release_resource(context, cmd_queue, program, kernel, mem_obj);
		return 0;
	}

	// 创建kernel，kernal是CL中对执行在一个最小粒度的compute item上的代码及参数的抽象（你可以理解成为cpu上的main函数）
	kernel = clCreateKernel(program, "gpu_add", NULL);

	if (kernel == NULL) {
		cout << "Failed to create kernel" << endl;
		return 0;
	}

	// 准备host 内存数据结构
	float f_result[TEST_DATA_LEN];
	float f_a[TEST_DATA_LEN];
	float f_b[TEST_DATA_LEN];

	//测试数据初始化
	for (int i = 0; i < TEST_DATA_LEN; ++i) {
		f_a[i] = i;
		f_b[i] = 2 * i + 1;
	}

	// 申请device端buffer数据内存
	if (create_memobj(context, mem_obj, f_a, f_b)) {
		release_resource(context, cmd_queue, program, kernel, mem_obj);
		return -1;
	}

	// 设置kernel程序的参数，mem_obj[0]，mem_obj[1]，mem_obj[2]分别对应add.cl中gpu_add函数的三个参数
	status = clSetKernelArg(kernel, 0, sizeof(cl_mem), &mem_obj[0]);
	status |= clSetKernelArg(kernel, 1, sizeof(cl_mem), &mem_obj[1]);
	status |= clSetKernelArg(kernel, 2, sizeof(cl_mem), &mem_obj[2]);

	if (status != CL_SUCCESS) {
		cout << " Error setting kernel arguments" << endl;
		release_resource(context, cmd_queue, program, kernel, mem_obj);
		return -1;
	}

	//设置工作项的工作尺寸
	size_t local_work_size[1] = {1};
	size_t global_work_size[1] = {TEST_DATA_LEN};

	// 调用API 运行kernel程序
	status = clEnqueueNDRangeKernel(cmd_queue, kernel, 1, 0,
			global_work_size, local_work_size, 0, NULL, NULL);

	if (status != CL_SUCCESS) {
		cout << "Error queuing kernel for excution" << endl;
		release_resource(context, cmd_queue, program, kernel, mem_obj);
		return -1;
	}

	// 读取mem_obj[2]中的数据到f_result中
	status = clEnqueueReadBuffer(cmd_queue, mem_obj[2], CL_TRUE, 0,
			TEST_DATA_LEN * sizeof(float), f_result, 0, NULL,
			NULL);

	if (status != CL_SUCCESS) {
		cout << "Error queuing kernel for excution" << endl;
		release_resource(context, cmd_queue, program, kernel, mem_obj);
		return -1;
	}

	//打印f_result结果
	for (int i = 0; i < TEST_DATA_LEN; i++) {
		printf("%.3f ", f_result[i]);
	}

	cout << endl;
	//释放申请的各个资源
	release_resource(context, cmd_queue, program, kernel, mem_obj);

	return 0;
}
